package org.unidata.mdm.meta.service.impl;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.context.DataRecordContext;
import org.unidata.mdm.core.type.model.AttributeElement;
import org.unidata.mdm.core.type.model.EntityElement;
import org.unidata.mdm.core.type.model.support.AttributeValueGenerator;
import org.unidata.mdm.core.type.model.support.ExternalIdValueGenerator;
import org.unidata.mdm.system.service.AfterPlatformStartup;
import org.unidata.mdm.system.service.ModuleService;

/**
 * @author Mikhail Mikhailov on May 16, 2020
 */
@Component
public class CustomValueGeneratingComponent implements AfterPlatformStartup {

    private ConcurrentMap<Class<?>, ValueGeneratingProxy<?>> instances = new ConcurrentHashMap<>();

    @Autowired
    private ModuleService moduleService;

    private AtomicBoolean initialized = new AtomicBoolean(false);
    /**
     * Constructor.
     */
    public CustomValueGeneratingComponent() {
        super();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPlatformStartup() {

        if (!initialized.compareAndSet(false, true)) {
            return;
        }

        instances.forEach((klass, proxy) -> {
            if (!proxy.isInitialized()) {
                proxy.setDelegate(processBean(klass));
                proxy.setInitialized(true);
            }
        });
    }
    /**
     * Loads and defines generator class.
     * @param className the name of the class
     * @return generator instance
     */
    public AttributeValueGenerator defineAttributeCustomValueGenerator(Class<?> klass) {
        return (AttributeValueGenerator) instances.computeIfAbsent(klass, k -> {
            if (initialized.get()) {
                return new AttributeValueGeneratingProxy(processBean(k), true);
            } else {
                return new AttributeValueGeneratingProxy(null, false);
            }
        });
    }
    /**
     * Loads and defines generator class.
     * @param className the name of the class
     * @return generator instance
     */
    public ExternalIdValueGenerator defineEntityCustomValueGenerator(Class<?> klass) {
        return (ExternalIdValueGenerator) instances.computeIfAbsent(klass, k -> {
            if (initialized.get()) {
                return new ExternalIdValueGeneratorProxy(processBean(k), true);
            } else {
                return new ExternalIdValueGeneratorProxy(null, false);
            }
        });
    }
    /**
     * Does usual Spring autowire magic.
     * @param <T> return type
     * @param klass the source class
     * @return result
     */
    @SuppressWarnings("unchecked")
    private <T> T processBean(Class<?> klass) {
        return (T) moduleService.createRuntimeSingleton(klass);
    }
    /**
     * The root proxy.
     */
    private class ValueGeneratingProxy<T> {
        /**
         * The delegate.
         */
        private T delegate;
        /**
         * Initialized.
         */
        private boolean initialized;
        /**
         * Constructor.
         * @param delegate the worker delegate.
         */
        public ValueGeneratingProxy(T delegate, boolean initialized) {
            super();
            this.delegate = delegate;
            this.initialized = initialized;
        }
        /**
         * @return the initialized
         */
        public boolean isInitialized() {
            return initialized;
        }
        /**
         * @param initialized the initialized to set
         */
        public void setInitialized(boolean initialized) {
            this.initialized = initialized;
        }
        /**
         * @return the delegate
         */
        public T getDelegate() {
            return delegate;
        }
        /**
         * @param delegate the delegate to set
         */
        public void setDelegate(T delegate) {
            this.delegate = delegate;
        }
    }
    /**
     * AVG proxy.
     */
    private class AttributeValueGeneratingProxy
        extends ValueGeneratingProxy<AttributeValueGenerator>
        implements AttributeValueGenerator {
        /**
         * Constructor.
         * @param delegate the delegate
         */
        public AttributeValueGeneratingProxy(AttributeValueGenerator delegate, boolean initialized) {
            super(delegate, initialized);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public Object generate(AttributeElement attribute, DataRecordContext input) {
            return getDelegate().generate(attribute, input);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public Class<?> getOutputType() {
            return getDelegate().getOutputType();
        }
    }
    /**
     * EIVG proxy.
     */
    private class ExternalIdValueGeneratorProxy
        extends ValueGeneratingProxy<ExternalIdValueGenerator>
        implements ExternalIdValueGenerator {
        /**
         * Constructor.
         * @param delegate the delegate
         */
        public ExternalIdValueGeneratorProxy(ExternalIdValueGenerator delegate, boolean initialized) {
            super(delegate, initialized);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public String generate(EntityElement entity, DataRecordContext input) {
            return getDelegate().generate(entity, input);
        }
    }
}
